//    Copyright (C) Kherty.  All rights reserved.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Diagnostics.Contracts;
using System.Reflection;
using OpenLS.Core.Serialization;

namespace OpenLS.Spreadsheet
{
    ///<summary>
    /// Base class for all types fo sheets. Descendants include Worksheet and Chartsheet.
    ///</summary>
    public abstract class Sheet: IOfficeXmlSerializable, INotifyPropertyChanged
    {

        internal abstract IEnumerable<CustomSheetView> GetCustomSheetViews();
        internal abstract string UriPrefix { get;}
        internal abstract string ContentType { get;}
        internal abstract string RelationshipType { get;}
        internal Sheet(Workbook wb, SheetInfo info)
        {
            _workbook = wb;
            _name = info.Name;
            info.CopyTo(this);
        }
        internal bool _isVisible = true;

        ///<summary>
        /// Get or set whether the sheet is visible in the workbook.
        ///</summary>
        ///<exception cref="NotSupportedException"></exception>
        public bool IsVisible
        {
            get { return _isVisible; }
            set
            {
                if (!value)
                {
                    bool found = false;
                    foreach (Sheet ws in _workbook.Sheets)
                    {
                        found = ws.IsVisible && ws != this;
                        if (found) break;
                    }
                    if (!found)
                        throw new NotSupportedException();
                }
                _isVisible = value;
                if (_workbook != null)
                    _workbook.Document.SetDirty();
                if (!_isVisible && Workbook.View.ActiveSheet == this)
                {

                    int index = Workbook.Sheets.IndexOf(this);
                    bool found = false;
                    for (int i = index + 1; i < Workbook.Sheets.Count; i++)
                    {
                        if (Workbook.Sheets[i].IsVisible)
                        {
                            Workbook.View.ActiveSheet = Workbook.Sheets[i];
                            found = true;
                            break;
                        }
                    }
                    if (!found)
                    {
                        for (int i = index - 1; i > 0; i--)
                        {
                            if (Workbook.Sheets[i].IsVisible)
                            {
                                Workbook.View.ActiveSheet = Workbook.Sheets[i];
                                found = true;
                                break;
                            }
                        }
                    }

                }
                NotifyPropertyChanged("IsVisible");
            }
        }

        ///<summary>
        /// Get or set the name of the sheet.
        ///</summary>
        ///<exception cref="NullReferenceException"></exception>
        ///<exception cref="ArgumentException"></exception>
        public string Name
        {
            get { return _name; }
            set
            {
                if (value == null)
                    throw new NullReferenceException();
                if (value.Length >= 32)
                    throw new ArgumentOutOfRangeException("Name length should be < 32"); //\\loc
                if (value.IndexOfAny(new[]{'[',  ']',  '*' ,'/' ,'\\' ,'?' ,':'})>= 0)
                    throw new ArgumentException("value");
                foreach (Sheet sh in Workbook.Sheets)
                {
                    if (sh == this)
                        continue;
                    if (string.Compare(sh.Name, value, true) == 0)
                        throw new ArgumentException("value");
                }
                _name = value;
                if (_workbook != null)
                    _workbook.Document.SetDirty();
                NotifyPropertyChanged("Name");
            }
        }
        private string _name;
        internal string SafeName
        {
            get
            {
                return GetSafeName(Workbook.InternalPrefix + Name);
            }
        }
        internal static string GetSafeName(string name)
        {
            bool needsQuote =!Enumerable.All(name, c => char.IsLetter(c) || char.IsDigit(c)) || name.Length == 0 || !char.IsLetter(name[0]);
            /* bool needsQuote = false;
            foreach (char c in name)
            {
                if (char.IsLetter(c))
                {
                    continue;
                }
                needsQuote = true;
                break;
            }*/
            if (needsQuote )
                return "'" + name.Replace("'", "''") + "'";
            return name;
        }


        private readonly Workbook _workbook;

        ///<summary>
        /// Get the workbook which contains the current sheet.
        ///</summary>
        public Workbook Workbook
        {
            get { return _workbook; }
        }
        internal void NotifyPropertyChanged(string propertyName)
        {
            Contract.Requires(GetType().GetProperty(propertyName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) != null);
            invokePropertyChanged(new PropertyChangedEventArgs(propertyName));
        }

        #region INotifyPropertyChanged Members
#if DEBUG2
        private PropertyChangedEventHandler _propertyChanged;
        public event PropertyChangedEventHandler PropertyChanged
        {
            add
            {
                Debug.WriteLine(string.Format(  "Adding PropertyChanged to {0}, count = {1}", this, 
                    _propertyChanged!= null? _propertyChanged.GetInvocationList().Length:0));
                _propertyChanged = (PropertyChangedEventHandler)Delegate.Combine(_propertyChanged, value);
            }
            remove { _propertyChanged = (PropertyChangedEventHandler)Delegate.Remove(_propertyChanged, value); }
        }

        private void invokePropertyChanged(PropertyChangedEventArgs e)
        {
            PropertyChangedEventHandler changed = _propertyChanged;
            if (changed != null) changed(this, e);
        }
#else
        public event PropertyChangedEventHandler PropertyChanged;
        private void invokePropertyChanged(PropertyChangedEventArgs e)
        {
            PropertyChangedEventHandler changed = PropertyChanged;
            if (changed != null) changed(this, e);
        }
#endif

        #endregion


        #region IOfficeXmlSerializable Members

        void IOfficeXmlSerializable.ReadXml(ReadContext context)
        {
            ReadXmlCore(context);
        }

        internal abstract void ReadXmlCore(ReadContext context);

        void IOfficeXmlSerializable.WriteXml(WriteContext context)
        {
            WriteXmlCore(context);
        }

        internal abstract void WriteXmlCore(WriteContext context);

        #endregion

        internal void SetName(string name)
        {
            _name = name;
        }
    }
}
